
pub struct ComponentableContext<'a> {
    pub lua_engine: &'a mut LuaEngine,
}
pub trait Componentable {
    fn tick(&mut self, delta_time: f32, context: &ComponentableContext) {

    }
    fn post_load(&mut self, parent_object: Rc<RefCell<dyn Object>>, context: &mut ComponentableContext) {

    }
    fn add_component(&mut self, rc_ref_self: Rc<RefCell<dyn Object>>, context: &mut ComponentableContext) {

    }
}

pub trait SlowTickAble {
    fn slow_tick(delta_time: f32);
}
use std::{cell::RefCell, rc::Rc};

// use mlua::FromLua;

use lua::core::LuaEngine;

use crate::vulkan::vulkan_rhi_utils::{VulkanRHI, GpuBuffer, UniformBuffer, PerObjectUniformData};

pub trait Drawable {
    fn draw(&self, vulkan_rhi: &VulkanRHI, command_buffer_index: usize);
}

#[derive(Clone)]
pub struct DrawCommandData {
    pub vertices: Option<GpuBuffer>,
    pub indices: Option<GpuBuffer>,
    pub descriptor_sets: Option<Vec<Vec<ash::vk::DescriptorSet>>>,
    pub index_len: u32
}
impl Drawable for DrawCommandData {
    fn draw(&self, vulkan_rhi: &VulkanRHI, command_buffer_index: usize) {
        if let (Some(vertex_buffer), Some(index_buffer), Some(descriptor_sets)) = (&self.vertices, &self.indices, &self.descriptor_sets) {
            unsafe {
                vulkan_rhi.logic_device
                    .cmd_bind_vertex_buffers(vulkan_rhi.command_buffers[command_buffer_index], 0, &[vertex_buffer.buffer], &[0]);
                vulkan_rhi.logic_device
                    .cmd_bind_index_buffer(vulkan_rhi.command_buffers[command_buffer_index], index_buffer.buffer, 0, ash::vk::IndexType::UINT32);
                vulkan_rhi.logic_device
                    .cmd_bind_descriptor_sets(vulkan_rhi.command_buffers[command_buffer_index], ash::vk::PipelineBindPoint::GRAPHICS, vulkan_rhi.pipeline_layout, 1, &descriptor_sets[command_buffer_index], &[]);
                vulkan_rhi.logic_device
                    .cmd_draw_indexed(vulkan_rhi.command_buffers[command_buffer_index], self.index_len, 1, 0, 0, 0);
            }
        } else {
            println!("[Render] Unable to Draw");
        }
    }
}
impl DrawCommandData {
    pub fn release_resource(&mut self, vulkan_rhi: &VulkanRHI) {
        if let Some(vertex_data) = &self.vertices { unsafe {
            vulkan_rhi.logic_device.destroy_buffer(vertex_data.buffer, None);
            vulkan_rhi.logic_device.free_memory(vertex_data.memory, None);
        }};
        if let Some(index_data) = &self.indices { unsafe {
            vulkan_rhi.logic_device.destroy_buffer(index_data.buffer, None);
            vulkan_rhi.logic_device.free_memory(index_data.memory, None);
        }};
        if let Some(descriptor_sets) = &self.descriptor_sets { 
            for descriptor_set in descriptor_sets {
                unsafe {
                    vulkan_rhi.logic_device.free_descriptor_sets(vulkan_rhi.descriptor_pool, descriptor_set).expect("failed to free descriptor sets");
                }
            }
        };
    }
}

pub struct UpdateableContext<'a> {
    pub vulkan_rhi: &'a VulkanRHI,
    pub frame_index: usize,
    pub delta_time: f32,
    pub time_duration: f32,
}
pub trait Updateable {
    fn update(&mut self, context: &UpdateableContext);
}

#[derive(Clone)]
pub struct UniformData {
    pub gpu_buffer: Option<UniformBuffer<PerObjectUniformData>>,
    pub model_matrix: cgmath::Matrix4<f32>,
    pub is_animated: f32
}
impl Updateable for UniformData {
    fn update(&mut self, context: &UpdateableContext) {
        if let Some(uniform_buffer) = &self.gpu_buffer { 
            let ubo = PerObjectUniformData {
                model: self.model_matrix,
                is_animated: self.is_animated
            };
    
            unsafe {
                uniform_buffer.mapped_handle[context.frame_index].copy_from_nonoverlapping(&ubo, 1);
            }
        }
    }
}
impl UniformData {
    pub fn release_resource(&mut self, vulkan_rhi: &VulkanRHI) {
        if let Some(uniform_buffers) = &self.gpu_buffer { 
            uniform_buffers.release_resource(vulkan_rhi);
        };
    }
}

pub trait Object: Drawable + Updateable + Componentable {
    fn release_resource(&mut self, vulkan_rhi: &VulkanRHI);
}
pub struct Scene {
    objects: Vec<Rc<RefCell<dyn Object>>>
}
impl Scene {
    pub fn new() -> Self {
        Self { objects: vec![] }
    }
    pub fn from_vec(vec: Vec<Rc<RefCell<dyn Object>>>) -> Self {
        Self { objects: vec }
    }
    pub fn size(&self) -> usize {
        self.objects.len()
    }

    pub fn release_resource(&mut self, vulkan_rhi: &VulkanRHI) {
        for object in &mut self.objects {
            object.borrow_mut().release_resource(vulkan_rhi);
        }
    }
    pub fn add(&mut self, object: Rc<RefCell<dyn Object>>, context: &mut ComponentableContext) {
        self.add_list(&mut vec![object], context);
    }
    pub fn add_list(&mut self, objects: &mut Vec<Rc<RefCell<dyn Object>>>, context: &mut ComponentableContext) {
        let current_list_len = self.objects.len();
        let append_list_len = objects.len();

        self.objects.append(objects);
        // post load
        for object in &mut self.objects[current_list_len..current_list_len+append_list_len] {
            object.borrow_mut().add_component(object.clone(), context);
        }
    }
}
impl Drawable for Scene {
    fn draw(&self, vulkan_rhi: &VulkanRHI, command_buffer_index: usize) {
        for object in &self.objects {
            object.borrow().draw(vulkan_rhi, command_buffer_index);
        }
    }
}
impl Updateable for Scene {
    fn update(&mut self, context: &UpdateableContext) {
        for object in &mut self.objects {
            object.borrow_mut().update(context);
        }
    }
}
impl Componentable for Scene {
    fn tick(&mut self, delta_time: f32, context: &ComponentableContext) {
        for object in &mut self.objects {
            object.borrow_mut().tick(delta_time, context);
        }
    }
}